﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MathNet.Numerics.LinearAlgebra.Double;
using System.IO;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Drawing;
using System.Windows.Forms;
using MathNet.Numerics.LinearAlgebra.Generic;
using System.Drawing.Imaging;

namespace T4PCA
{
    public class Point
    {
        private DenseVector bits;
        //private DenseVector currentBit;
        private int label;

        /// <summary>
        /// Gets or sets the bits of the point.
        /// </summary>
        /// <value>
        /// The bits.
        /// </value>
        public DenseVector Bits
        {
            get { return bits; }
            private set { bits = value; }
        }

        ///// <summary>
        ///// Gets or sets the current bit.
        ///// </summary>
        ///// <value>
        ///// The current bit.
        ///// </value>
        //public DenseVector CurrentBit
        //{
        //    get { return currentBit; }
        //    set { currentBit = value; }
        //}
        
        /// <summary>
        /// Gets the label of the point.
        /// </summary>
        public int Label
        {
            get { return label; }
            private set { }
        }

        public static bool operator ==(Point leftSide, Point rightSide)
        {
            return leftSide.Equals(rightSide);
        }
        public static bool operator !=(Point leftSide, Point rightSide)
        {
            return !leftSide.Equals(rightSide);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Point))
                return false;
            var rightSide = obj as Point;
            return this.Bits == rightSide.Bits && this.Label == rightSide.Label;
        }
        
        /// <summary>
        /// Initializes a new instance of the <see cref="Point"/> class.
        /// </summary>
        /// <param name="s">The line of the file.</param>
        public Point(string s)
        {
            var data = s.Split(new char[] { ' ' });
            var _bits = (from b in data.Take(256)
                         select double.Parse(b)).ToArray();
            bits = new DenseVector(_bits);
            var lbl = data.Skip(256).ToArray();
            for (int i = 0; i < lbl.Length; i++)
            {
                if (lbl[i] == "1")
                    label = i;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {

            var str = File.CreateText("out.csv");

            var seed = new Random();
            var text = File.ReadAllLines("semeion.data");
            var points =
                   (
                       from line in text
                       where line != ""
                       select new Point(line)
                   ).OrderBy(p => p.Label).ToList();

            var ite = 1;
            var errors = (from i in Enumerable.Range(0, ite) select new DenseVector(256)).ToArray();

            for (int iteration = 0; iteration < ite; iteration++)
            {
                Console.WriteLine("iteration n. " + iteration);

                var cats =
                    (
                        from i in Enumerable.Range(0, 10)
                        select
                            (
                                from p in points
                                where p.Label == i
                                select p
                            ).ToList()
                    ).ToArray();

                var train = new List<Point>();
                var test = new List<Point>();

                var selected = (from i in Enumerable.Range(0, cats.Length) select 10).ToArray();

                for (int i = 0; i < selected.Length; i++)
                {
                    for (int j = 0; j < selected[i]; j++)
                    {
                        var idx = seed.Next(cats[i].Count);
                        train.Add(cats[i][idx]);
                        cats[i].RemoveAt(idx);
                    }
                }

                foreach (var c in cats)
                {
                    foreach (var p in c)
                    {
                        test.Add(p);
                    }
                }

                Console.BackgroundColor = ConsoleColor.DarkMagenta;

                //int kkk = 0;

                //for (int i = 0, j = 0; i < points.Count && j < test.Count; i++, j++)
                //{
                //    var in_p = points[i];
                //    var test_p = test[j];
                //    if (in_p == test_p)
                //    {
                //        kkk++;
                //        Console.WriteLine("ok!! " + in_p.Label);
                //    }
                //    else
                //    {
                //        j--;
                //    }
                //}


                var matrixes = PCA(train, test);


                for (int i = 1; i < 256; i++)
                {
                    var results = KNN(matrixes, train, test, i);
                    Console.WriteLine("tested" + i);
                    errors[iteration][i] = results.Zip(test, ((label, p) => label == p.Label ? 1.0 : 0.0)).Sum() / (double)test.Count;
                }


            }

            var mu = mean(errors);

            for (int i = 0; i < errors[0].Count; i++)
            {
                str.WriteLine(i + ";" + mu[i]);
            }

            str.Close();
        }

        private static DenseVector mean(DenseVector[] vs)
        {
            return vs.Aggregate(new DenseVector(vs[0].Count), ((acc, p) => acc + p)).Divide(vs.Length) as DenseVector;
        }

        private static int[] KNN(Tuple<DenseMatrix, DenseMatrix> alpha_train_test, List<Point> train, List<Point> test, int k)
        {
            var beta_train = alpha_train_test.Item1.SubMatrix(0, alpha_train_test.Item1.RowCount, 0, k) as DenseMatrix;
            var beta_test = alpha_train_test.Item2.SubMatrix(0, alpha_train_test.Item2.RowCount, 0, k) as DenseMatrix;

            var dist_m = get_dist_matrix(beta_test, beta_train);

            return
                (
                    from i in Enumerable.Range(0, test.Count)
                    let r_i = dist_m.Row(i)
                    let min_i = r_i.MinimumIndex()
                    select train[min_i].Label
                ).ToArray();
        }

        private static DenseMatrix get_dist_matrix(DenseMatrix test, DenseMatrix train)
        {
            var a = (Ones(train.RowCount) * SumColumns(MatrixSquare(test).Transpose() as DenseMatrix)).Transpose() as DenseMatrix;
            var b = Ones(test.RowCount) * SumColumns(MatrixSquare(train).Transpose() as DenseMatrix);

            var quad = (2 * (test * train.Transpose()));

            var res = a + b - quad as DenseMatrix;

            return res;
        }

        private static DenseMatrix SumColumns(DenseMatrix m)
        {
            var res = new DenseMatrix(1, m.ColumnCount);
            for (int i = 0; i < m.ColumnCount; i++)
            {
                var sum = 0.0;
                for (int j = 0; j < m.RowCount; j++)
                {
                    sum += m[j, i];
                }
                res[0, i] = sum;
            }
            return res;
        }

        private static DenseMatrix Ones(int dim)
        {
            var res = new DenseMatrix(dim, 1);
            for (int i = 0; i < dim; i++)
            {
                res[i, 0] = 1;
            }
            return res;
        }

        private static DenseMatrix MatrixSquare(DenseMatrix m)
        {
            var res = new DenseMatrix(m.RowCount, m.ColumnCount);
            for (int i = 0; i < m.RowCount; i++)
            {
                for (int j = 0; j < m.ColumnCount; j++)
                {
                    res[i, j] = Math.Pow(m[i, j], 2);
                }
            }
            return res;
        }

        static Tuple<DenseMatrix, DenseMatrix> PCA(List<Point> train, List<Point> test)
        {
            DenseVector train_mean = train.Aggregate(new DenseVector(train[0].Bits.Count), ((acc, p) => acc + p.Bits)).Divide(train.Count) as DenseVector;
            //DenseVector test_mean = test.Aggregate(new DenseVector(test[0].Bits.Count), ((acc, p) => acc + p.Bits)).Divide(test.Count) as DenseVector;

            var eigen_v = GetEigen(train, train_mean);

            //var str = File.CreateText("points.csv");
            //str.WriteLine("Label;X1;X2");

            var alpha_train = AlphaMatrix(train_mean, eigen_v, train);//, str);
            var alpha_test = AlphaMatrix(train_mean, eigen_v, test);//, str);

            //str.Close();

            return new Tuple<DenseMatrix, DenseMatrix>(alpha_train, alpha_test);
        }

        private static DenseMatrix GetEigen(List<Point> points, DenseVector mean)
        {
            DenseMatrix co_variance =
                (
                    from tmp in points
                    let dif = tmp.Bits - mean
                    select DenseVector.OuterProduct(dif, dif)
                ).Aggregate(new DenseMatrix(points[0].Bits.Count), (acc, m) => acc + m) as DenseMatrix;
            var eigen = co_variance.Svd(true).VT();
            return eigen as DenseMatrix;
        }

        private static DenseMatrix AlphaMatrix(DenseVector mean, DenseMatrix eigen, List<Point> points)//, StreamWriter str)
        {
            DenseMatrix alpha_m = new DenseMatrix(points.Count, eigen.ColumnCount);
            for (int i = 0; i < alpha_m.RowCount; i++)
            {
                //str.Write(points[i].Label + ";");

                for (int j = 0; j < alpha_m.ColumnCount; j++)
                {
                    alpha_m[i, j] = (points[i].Bits - mean) * eigen.Row(j);
                    //if (j < 2)
                    //{
                    //    str.Write(alpha_m[i, j] + ";");
                    //}
                }
                //str.WriteLine();
            }
            return alpha_m;
        }
    }
}
